package main

import (
	"fmt"
	"io/ioutil"
	"math/rand"
	"net"
	"net/http"
	"net/http/httptest"
	"os"
	"testing"
)

//https://golang-tech-stack.com/tutorial/topic/golang-unit-testing
/*
单元测试 函数名前缀为Test
基准(压力)测试 函数名前缀Benchmark
实例测试 函数名前缀Example
模糊(随机)测试 函数名前缀为Fuzz
测试文件以_test.go结尾
func TestAbs(t *testing.T)

测试用例名称一般命名为Test加上待测试的方法名
运行go test，所有的test测试用例都会被执行
go test -v 参数会显示每个测试用例的测试结果，另外 -cover参数可以查看覆盖率
如果只想运行其中的一个用例，可以用-run参数指定，该参数支持通配符*，和部分正则表达式，例如^,$ go test -run TestDan -v
单元测试框架提供的日志方法
Log    打印日志，同时结束测试
Logf   格式化打印日志，同时结束测试
Error  打印错误日志，同时结束测试
Errorf 格式化打印错误日志，同时结束测试
Fatal  打印致命日志，同时结束测试
Fatalf 格式化打印致命日志，同时结束测试
*/
func Add(a, b int) int {
	return a + b
}

func Mul(a, b int) int {
	return a * b
}

// 测试文件写
func TestDan(t *testing.T) {
	if ans := Add(1, 2); ans != 3 {
		t.Errorf("1+2 expected be 3,but %d got", ans)
	}
	if ans := Mul(-10, -2); ans != 20 {
		t.Errorf("-10*-2 expected be 20,but %d got", ans)
	}
}

func BenchmarkRandInt(b *testing.B) {
	for i := 0; i < b.N; i++ {
		rand.Int()
	}
}

// 子测试，可以在某个测试用例中，根据测试场景使用t.Run创建不同的子测试用例
// 之前的例子测试失败时使用t.Error/t.Errorf,这个例子中使用t.Fatal/t.Fatalf,区别在于前者遇错不停，还会继续执行其他测试用例，后者遇错即停
func TestMul(t *testing.T) {
	t.Run("pos", func(t *testing.T) {
		if Mul(2, 3) != 7 {
			t.Fatal("fail")
		}
	})
	t.Run("neg", func(t *testing.T) {
		if Mul(2, -3) != -6 {
			t.Fatal("fail")
		}
	})
}

// 多个子测试的场景，推荐如下的写法
func TestMull(t *testing.T) {
	cases := []struct {
		Name           string
		A, B, Expected int
	}{
		{"pos", 2, 3, 6},
		{"neg", 2, -3, -6},
		{"zero", 2, 0, 0},
	}
	for _, c := range cases {
		t.Run(c.Name, func(t *testing.T) {
			if ans := Mul(c.A, c.B); ans != c.Expected {
				t.Fatalf("%d * %d expected %d,but %d got", c.A, c.B, c.Expected, ans)
			}
		})
	}
}

// 帮助函数 对一些重复的逻辑，抽取出来作为公共的帮助函数，可以增加测试代码的可读性和可维护性
type Casel struct {
	A, B, Expected int
}

func CreateTestCasel(t *testing.T, c *Casel) {
	//t.Helper()
	if ans := Mul(c.A, c.B); ans != c.Expected {
		t.Fatalf("%d * %d expected %d,but %d got", c.A, c.B, c.Expected, ans)
	}
}
func TestMult(t *testing.T) {
	CreateTestCasel(t, &Casel{2, 3, 6})
	CreateTestCasel(t, &Casel{2, -3, -6})
	CreateTestCasel(t, &Casel{2, 0, 1}) //Error
}

// setup与teardown
// 如果测试文件中包含TeatMain，那么生成的测试将调用TestMain(m)，而不是直接运行
// 调用m.run()触发所有测试用例的执行，并使用os.Exit()处理返回的状态码，如果不为0，说明有用例失败
func setup() {
	fmt.Println("before all tests")
}
func teardown() {
	fmt.Println("after all tests")
}
func Test1(t *testing.T) {
	fmt.Println("i an test1")
}
func Test2(t *testing.T) {
	fmt.Println("i an test2")
}
func TestMain(m *testing.M) {
	setup()
	code := m.Run()
	teardown()
	os.Exit(code)
}

// 案例1，测试网络
func HelloHandler(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("Hello World"))
}
func HandError(t *testing.T, err error) {
	t.Helper()
	if err != nil {
		t.Fatal("fail", err)
	}
}
func TestConn(t *testing.T) {
	conn, err := net.Listen("tcp", "127.0.0.1:8082")
	HandError(t, err)
	defer conn.Close()

	http.HandleFunc("/hello", HelloHandler)
	go http.Serve(conn, nil)

	resp, err := http.Get("http://" + conn.Addr().String() + "/hello")
	HandError(t, err)

	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	HandError(t, err)

	if string(body) != "Hello World" {
		t.Fatal("expected hello worls ,but got", string(body))
	}
}

// httptest
func TestConnl(t *testing.T) {
	req := httptest.NewRequest("GET", "http://example.com/foo", nil)
	w := httptest.NewRecorder()
	HelloHandler(w, req)
	bytes, _ := ioutil.ReadAll(w.Result().Body)

	if string(bytes) != "Hello World" {
		t.Fatal("expected hello world, but got", string(bytes))
	}
}
